4 * Test class for Revision storage.
6 * @group ContentHandler
8 * ^--- important, causes temporary tables to be used instead of the real database
11 * ^--- important, causes tests not to fail with timeout
13 class RevisionStorageTest
extends MediaWikiTestCase
{
15 * @var WikiPage $the_page
19 public function __construct( $name = null, array $data = [], $dataName = '' ) {
20 parent
::__construct( $name, $data, $dataName );
22 $this->tablesUsed
= array_merge( $this->tablesUsed
,
41 protected function setUp() {
42 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
46 $wgExtraNamespaces[12312] = 'Dummy';
47 $wgExtraNamespaces[12313] = 'Dummy_talk';
49 $wgNamespaceContentModels[12312] = 'DUMMY';
50 $wgContentHandlers['DUMMY'] = 'DummyContentHandlerForTesting';
52 MWNamespace
::clearCaches();
53 // Reset namespace cache
54 $wgContLang->resetNamespaces();
55 if ( !$this->the_page
) {
56 $this->the_page
= $this->createPage(
57 'RevisionStorageTest_the_page',
59 CONTENT_MODEL_WIKITEXT
63 $this->tablesUsed
[] = 'archive';
66 protected function tearDown() {
67 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
71 unset( $wgExtraNamespaces[12312] );
72 unset( $wgExtraNamespaces[12313] );
74 unset( $wgNamespaceContentModels[12312] );
75 unset( $wgContentHandlers['DUMMY'] );
77 MWNamespace
::clearCaches();
78 // Reset namespace cache
79 $wgContLang->resetNamespaces();
82 protected function makeRevision( $props = null ) {
83 if ( $props === null ) {
87 if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) {
88 $props['text'] = 'Lorem Ipsum';
91 if ( !isset( $props['comment'] ) ) {
92 $props['comment'] = 'just a test';
95 if ( !isset( $props['page'] ) ) {
96 $props['page'] = $this->the_page
->getId();
99 $rev = new Revision( $props );
101 $dbw = wfGetDB( DB_MASTER
);
102 $rev->insertOn( $dbw );
108 * @param string $titleString
109 * @param string $text
110 * @param string|null $model
114 protected function createPage( $titleString, $text, $model = null ) {
115 if ( !preg_match( '/:/', $titleString ) &&
116 ( $model === null ||
$model === CONTENT_MODEL_WIKITEXT
)
118 $ns = $this->getDefaultWikitextNS();
119 $titleString = MWNamespace
::getCanonicalName( $ns ) . ':' . $titleString;
122 $title = Title
::newFromText( $titleString );
123 $wikipage = new WikiPage( $title );
125 // Delete the article if it already exists
126 if ( $wikipage->exists() ) {
127 $wikipage->doDeleteArticle( "done" );
130 $content = ContentHandler
::makeContent( $text, $title, $model );
131 $wikipage->doEditContent( $content, __METHOD__
, EDIT_NEW
);
136 protected function assertRevEquals( Revision
$orig, Revision
$rev = null ) {
137 $this->assertNotNull( $rev, 'missing revision' );
139 $this->assertEquals( $orig->getId(), $rev->getId() );
140 $this->assertEquals( $orig->getPage(), $rev->getPage() );
141 $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
142 $this->assertEquals( $orig->getUser(), $rev->getUser() );
143 $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
144 $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
145 $this->assertEquals( $orig->getSha1(), $rev->getSha1() );
149 * @covers Revision::__construct
151 public function testConstructFromRow() {
152 $orig = $this->makeRevision();
154 $dbr = wfGetDB( DB_REPLICA
);
155 $res = $dbr->select( 'revision', Revision
::selectFields(), [ 'rev_id' => $orig->getId() ] );
156 $this->assertTrue( is_object( $res ), 'query failed' );
158 $row = $res->fetchObject();
161 $rev = new Revision( $row );
163 $this->assertRevEquals( $orig, $rev );
167 * @covers Revision::newFromTitle
169 public function testNewFromTitle_withoutId() {
170 $page = $this->createPage(
173 CONTENT_MODEL_WIKITEXT
175 $latestRevId = $page->getLatest();
177 $rev = Revision
::newFromTitle( $page->getTitle() );
179 $this->assertTrue( $page->getTitle()->equals( $rev->getTitle() ) );
180 $this->assertEquals( $latestRevId, $rev->getId() );
184 * @covers Revision::newFromTitle
186 public function testNewFromTitle_withId() {
187 $page = $this->createPage(
190 CONTENT_MODEL_WIKITEXT
192 $latestRevId = $page->getLatest();
194 $rev = Revision
::newFromTitle( $page->getTitle(), $latestRevId );
196 $this->assertTrue( $page->getTitle()->equals( $rev->getTitle() ) );
197 $this->assertEquals( $latestRevId, $rev->getId() );
201 * @covers Revision::newFromTitle
203 public function testNewFromTitle_withBadId() {
204 $page = $this->createPage(
207 CONTENT_MODEL_WIKITEXT
209 $latestRevId = $page->getLatest();
211 $rev = Revision
::newFromTitle( $page->getTitle(), $latestRevId +
1 );
213 $this->assertNull( $rev );
217 * @covers Revision::newFromRow
219 public function testNewFromRow() {
220 $orig = $this->makeRevision();
222 $dbr = wfGetDB( DB_REPLICA
);
223 $res = $dbr->select( 'revision', Revision
::selectFields(), [ 'rev_id' => $orig->getId() ] );
224 $this->assertTrue( is_object( $res ), 'query failed' );
226 $row = $res->fetchObject();
229 $rev = Revision
::newFromRow( $row );
231 $this->assertRevEquals( $orig, $rev );
235 * @covers Revision::newFromArchiveRow
237 public function testNewFromArchiveRow() {
238 $page = $this->createPage(
239 'RevisionStorageTest_testNewFromArchiveRow',
241 CONTENT_MODEL_WIKITEXT
243 $orig = $page->getRevision();
244 $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
246 $dbr = wfGetDB( DB_REPLICA
);
248 'archive', Revision
::selectArchiveFields(), [ 'ar_rev_id' => $orig->getId() ]
250 $this->assertTrue( is_object( $res ), 'query failed' );
252 $row = $res->fetchObject();
255 $rev = Revision
::newFromArchiveRow( $row );
257 $this->assertRevEquals( $orig, $rev );
261 * @covers Revision::newFromId
263 public function testNewFromId() {
264 $orig = $this->makeRevision();
266 $rev = Revision
::newFromId( $orig->getId() );
268 $this->assertRevEquals( $orig, $rev );
272 * @covers Revision::fetchRevision
274 public function testFetchRevision() {
275 $page = $this->createPage(
276 'RevisionStorageTest_testFetchRevision',
278 CONTENT_MODEL_WIKITEXT
281 // Hidden process cache assertion below
282 $page->getRevision()->getId();
284 $page->doEditContent( new WikitextContent( 'two' ), 'second rev' );
285 $id = $page->getRevision()->getId();
287 $res = Revision
::fetchRevision( $page->getTitle() );
289 # note: order is unspecified
291 while ( ( $row = $res->fetchObject() ) ) {
292 $rows[$row->rev_id
] = $row;
295 $this->assertEquals( 1, count( $rows ), 'expected exactly one revision' );
296 $this->assertArrayHasKey( $id, $rows, 'missing revision with id ' . $id );
300 * @covers Revision::selectFields
302 public function testSelectFields() {
303 global $wgContentHandlerUseDB;
305 $fields = Revision
::selectFields();
307 $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' );
308 $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' );
310 in_array( 'rev_timestamp', $fields ),
311 'missing rev_timestamp in list of fields'
313 $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' );
315 if ( $wgContentHandlerUseDB ) {
316 $this->assertTrue( in_array( 'rev_content_model', $fields ),
317 'missing rev_content_model in list of fields' );
318 $this->assertTrue( in_array( 'rev_content_format', $fields ),
319 'missing rev_content_format in list of fields' );
324 * @covers Revision::getPage
326 public function testGetPage() {
327 $page = $this->the_page
;
329 $orig = $this->makeRevision( [ 'page' => $page->getId() ] );
330 $rev = Revision
::newFromId( $orig->getId() );
332 $this->assertEquals( $page->getId(), $rev->getPage() );
336 * @covers Revision::getContent
338 public function testGetContent_failure() {
339 $rev = new Revision( [
340 'page' => $this->the_page
->getId(),
341 'content_model' => $this->the_page
->getContentModel(),
342 'text_id' => 123456789, // not in the test DB
345 $this->assertNull( $rev->getContent(),
346 "getContent() should return null if the revision's text blob could not be loaded." );
348 // NOTE: check this twice, once for lazy initialization, and once with the cached value.
349 $this->assertNull( $rev->getContent(),
350 "getContent() should return null if the revision's text blob could not be loaded." );
354 * @covers Revision::getContent
356 public function testGetContent() {
357 $orig = $this->makeRevision( [ 'text' => 'hello hello.' ] );
358 $rev = Revision
::newFromId( $orig->getId() );
360 $this->assertEquals( 'hello hello.', $rev->getContent()->getNativeData() );
364 * @covers Revision::getContentModel
366 public function testGetContentModel() {
367 global $wgContentHandlerUseDB;
369 if ( !$wgContentHandlerUseDB ) {
370 $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
373 $orig = $this->makeRevision( [ 'text' => 'hello hello.',
374 'content_model' => CONTENT_MODEL_JAVASCRIPT
] );
375 $rev = Revision
::newFromId( $orig->getId() );
377 $this->assertEquals( CONTENT_MODEL_JAVASCRIPT
, $rev->getContentModel() );
381 * @covers Revision::getContentFormat
383 public function testGetContentFormat() {
384 global $wgContentHandlerUseDB;
386 if ( !$wgContentHandlerUseDB ) {
387 $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' );
390 $orig = $this->makeRevision( [
391 'text' => 'hello hello.',
392 'content_model' => CONTENT_MODEL_JAVASCRIPT
,
393 'content_format' => CONTENT_FORMAT_JAVASCRIPT
395 $rev = Revision
::newFromId( $orig->getId() );
397 $this->assertEquals( CONTENT_FORMAT_JAVASCRIPT
, $rev->getContentFormat() );
401 * @covers Revision::isCurrent
403 public function testIsCurrent() {
404 $page = $this->createPage(
405 'RevisionStorageTest_testIsCurrent',
407 CONTENT_MODEL_WIKITEXT
409 $rev1 = $page->getRevision();
411 # @todo find out if this should be true
412 # $this->assertTrue( $rev1->isCurrent() );
414 $rev1x = Revision
::newFromId( $rev1->getId() );
415 $this->assertTrue( $rev1x->isCurrent() );
417 $page->doEditContent(
418 ContentHandler
::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT
),
421 $rev2 = $page->getRevision();
423 # @todo find out if this should be true
424 # $this->assertTrue( $rev2->isCurrent() );
426 $rev1x = Revision
::newFromId( $rev1->getId() );
427 $this->assertFalse( $rev1x->isCurrent() );
429 $rev2x = Revision
::newFromId( $rev2->getId() );
430 $this->assertTrue( $rev2x->isCurrent() );
434 * @covers Revision::getPrevious
436 public function testGetPrevious() {
437 $page = $this->createPage(
438 'RevisionStorageTest_testGetPrevious',
439 'Lorem Ipsum testGetPrevious',
440 CONTENT_MODEL_WIKITEXT
442 $rev1 = $page->getRevision();
444 $this->assertNull( $rev1->getPrevious() );
446 $page->doEditContent(
447 ContentHandler
::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT
),
448 'second rev testGetPrevious' );
449 $rev2 = $page->getRevision();
451 $this->assertNotNull( $rev2->getPrevious() );
452 $this->assertEquals( $rev1->getId(), $rev2->getPrevious()->getId() );
456 * @covers Revision::getNext
458 public function testGetNext() {
459 $page = $this->createPage(
460 'RevisionStorageTest_testGetNext',
461 'Lorem Ipsum testGetNext',
462 CONTENT_MODEL_WIKITEXT
464 $rev1 = $page->getRevision();
466 $this->assertNull( $rev1->getNext() );
468 $page->doEditContent(
469 ContentHandler
::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT
),
470 'second rev testGetNext'
472 $rev2 = $page->getRevision();
474 $this->assertNotNull( $rev1->getNext() );
475 $this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() );
479 * @covers Revision::newNullRevision
481 public function testNewNullRevision() {
482 $page = $this->createPage(
483 'RevisionStorageTest_testNewNullRevision',
485 CONTENT_MODEL_WIKITEXT
487 $orig = $page->getRevision();
489 $dbw = wfGetDB( DB_MASTER
);
490 $rev = Revision
::newNullRevision( $dbw, $page->getId(), 'a null revision', false );
492 $this->assertNotEquals( $orig->getId(), $rev->getId(),
493 'new null revision shold have a different id from the original revision' );
494 $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
495 'new null revision shold have the same text id as the original revision' );
496 $this->assertEquals( 'some testing text', $rev->getContent()->getNativeData() );
500 * @covers Revision::insertOn
502 public function testInsertOn() {
503 $ip = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
505 $orig = $this->makeRevision( [
509 // Make sure the revision was copied to ip_changes
510 $dbr = wfGetDB( DB_REPLICA
);
511 $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $orig->getId() ] );
512 $row = $res->fetchObject();
514 $this->assertEquals( IP
::toHex( $ip ), $row->ipc_hex
);
515 $this->assertEquals( $orig->getTimestamp(), $row->ipc_rev_timestamp
);
518 public static function provideUserWasLastToEdit() {
519 yield
'actually the last edit' => [ 3, true ];
520 yield
'not the current edit, but still by this user' => [ 2, true ];
521 yield
'edit by another user' => [ 1, false ];
522 yield
'first edit, by this user, but another user edited in the mean time' => [ 0, false ];
526 * @dataProvider provideUserWasLastToEdit
528 public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
529 $userA = User
::newFromName( "RevisionStorageTest_userA" );
530 $userB = User
::newFromName( "RevisionStorageTest_userB" );
532 if ( $userA->getId() === 0 ) {
533 $userA = User
::createNew( $userA->getName() );
536 if ( $userB->getId() === 0 ) {
537 $userB = User
::createNew( $userB->getName() );
540 $ns = $this->getDefaultWikitextNS();
542 $dbw = wfGetDB( DB_MASTER
);
545 // create revisions -----------------------------
546 $page = WikiPage
::factory( Title
::newFromText(
547 'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
548 $page->insertOn( $dbw );
550 $revisions[0] = new Revision( [
551 'page' => $page->getId(),
552 // we need the title to determine the page's default content model
553 'title' => $page->getTitle(),
554 'timestamp' => '20120101000000',
555 'user' => $userA->getId(),
557 'content_model' => CONTENT_MODEL_WIKITEXT
,
558 'summary' => 'edit zero'
560 $revisions[0]->insertOn( $dbw );
562 $revisions[1] = new Revision( [
563 'page' => $page->getId(),
564 // still need the title, because $page->getId() is 0 (there's no entry in the page table)
565 'title' => $page->getTitle(),
566 'timestamp' => '20120101000100',
567 'user' => $userA->getId(),
569 'content_model' => CONTENT_MODEL_WIKITEXT
,
570 'summary' => 'edit one'
572 $revisions[1]->insertOn( $dbw );
574 $revisions[2] = new Revision( [
575 'page' => $page->getId(),
576 'title' => $page->getTitle(),
577 'timestamp' => '20120101000200',
578 'user' => $userB->getId(),
580 'content_model' => CONTENT_MODEL_WIKITEXT
,
581 'summary' => 'edit two'
583 $revisions[2]->insertOn( $dbw );
585 $revisions[3] = new Revision( [
586 'page' => $page->getId(),
587 'title' => $page->getTitle(),
588 'timestamp' => '20120101000300',
589 'user' => $userA->getId(),
591 'content_model' => CONTENT_MODEL_WIKITEXT
,
592 'summary' => 'edit three'
594 $revisions[3]->insertOn( $dbw );
596 $revisions[4] = new Revision( [
597 'page' => $page->getId(),
598 'title' => $page->getTitle(),
599 'timestamp' => '20120101000200',
600 'user' => $userA->getId(),
602 'content_model' => CONTENT_MODEL_WIKITEXT
,
603 'summary' => 'edit four'
605 $revisions[4]->insertOn( $dbw );
607 // test it ---------------------------------
608 $since = $revisions[$sinceIdx]->getTimestamp();
610 $wasLast = Revision
::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
612 $this->assertEquals( $expectedLast, $wasLast );